package com.example.grantsys.api;

import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.grantsys.domain.Terminal;
import com.example.grantsys.domain.User;
import com.example.grantsys.dto.UserDTO;
import com.example.grantsys.prop.JwtProp;
import com.example.grantsys.req.GrantReq;
import com.example.grantsys.resp.GrantResp;
import com.example.grantsys.restful.ResultCodeEnum;
import com.example.grantsys.restful.ResultVO;
import com.example.grantsys.service.TerminalService;
import com.example.grantsys.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Api(tags = "登录管理")
@RestController
public class GrantApi {

    @Autowired
    private JwtProp jwtProp;
    @Autowired
    private TerminalService terminalService;
    @Autowired
    private UserService userService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * {
     * "code": 0,
     * "msg": "操作成功",
     * "data": {
     * "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiY2xpZW50SWQiOiJjbG91ZF93ZWIiLCJpc3MiOiJncmFudC1zeXMiLCJuYW1lIjoiSm9uZSIsImV4cCI6MTYyMzA3MzAxMywiaWF0IjoxNjIzMDY1ODEzLCJhY2NvdW50IjoiYWFhIiwiZW1haWwiOiJ0ZXN0MUBiYW9taWRvdS5jb20iLCJhZ2UiOjE4fQ.hHZbiU2rovrBdHsVXQNTZKBLVpznGcWWxFO3K1et8z8",
     * "expire": 1623073013083
     * },
     * "timestamp": 1623065813083
     * }
     *
     * @param clientId
     * @param clientSecret
     * @param req
     * @return
     * @throws UnsupportedEncodingException
     */
    @PostMapping("/grantAccess")
    @ApiOperation("获取授权")
    public ResultVO<GrantResp> getGrantAccess(@RequestHeader String clientId,
                                              @RequestHeader String clientSecret,
                                              @RequestBody @Validated GrantReq req
    ) throws UnsupportedEncodingException {

        String onlineKey = "user_online:" + clientId + ":" + req.getAccount();
        if (stringRedisTemplate.hasKey(onlineKey)) {
            String accessToken = stringRedisTemplate.opsForValue().get(onlineKey);
            if (StringUtils.isNotBlank(accessToken) && stringRedisTemplate.hasKey("token:" + accessToken)) {
                Long expireSeconds = stringRedisTemplate.getExpire("token:" + accessToken, TimeUnit.SECONDS);
                GrantResp resp = new GrantResp();
                resp.setAccessToken(accessToken);
                resp.setExpire(DateUtil.offsetSecond(new Date(), expireSeconds.intValue()).getTime());
                return ResultVO.ok(resp);
            }
        }


        Terminal client = terminalService.getOne(
                new LambdaQueryWrapper<Terminal>().eq(Terminal::getName, clientId));
        if (client == null) {
            return ResultVO.fail(ResultCodeEnum.CLIENT_IS_NOT_EXIST);
        } else {
            if (!client.getSecret().equals(SecureUtil.md5(clientSecret))) {
                return ResultVO.fail(ResultCodeEnum.CLIENT_IS_NOT_EXIST);
            }

            User dbUser =
                    userService.getOne(
                            new LambdaQueryWrapper<User>()
                                    .eq(User::getAccount, req.getAccount())
                    );
            if (Objects.nonNull(dbUser)) {
                if (dbUser.getPassword().equals(SecureUtil.md5(req.getPassword()))) {

                    //JWT令牌由三部分组成，每部分中间使用点（.）分隔，比如：xxxxx.yyyyy.zzzzz
                    //Header.Payload.Signature

                    //Header头部包括令牌的类型（即JWT）及使用的哈希算法（如HMAC SHA256或RSA）一个例子如下：
                    //{
                    //"alg": "HS256",
                    //"typ": "JWT"
                    //}

                    //Payload负载，
                    // 内容也是一个json对象，它是存放有效信息的地方，它可以存放jwt提供的现成字段，
                    // 比如：iss（签发者）,exp（过期时间戳）, sub（面向的用户）等，也可自定义字段。此部分不建议存放敏感信息
                    //{
                    //"sub": "1234567890",
                    //"name": "456",
                    //"admin": true
                    //}

                    //Signature是签名，此部分用于防止jwt内容被篡改。
                    //这个部分使用base64url将前两部分进行编码，编码后使用点（.）连接组成字符串，最后使用header中声明签名算法进行签名。
                    //HMACSHA256(
                    //base64UrlEncode(header) + "." +
                    //base64UrlEncode(payload),
                    //secret
                    //)


                    //指定jwt使用的哈希算法
                    ////secret 密钥，只有服务器知道
                    Algorithm algorithm = Algorithm.HMAC256(jwtProp.getSecret());
                    Date now = new Date();
                    Date expireDate = DateUtil.offsetHour(now, jwtProp.getExpireHour());
                    String token = JWT.create()
                            .withIssuer(jwtProp.getIssuer()) //token发布者
                            .withIssuedAt(now) //token生成时间
                            .withExpiresAt(expireDate) //token有效期
                            .withSubject(dbUser.getId().toString())   //sub面向的用户
                            .withClaim("clientId", clientId)  //Claim 自定义声明 （key，value形式
                            .withClaim("account", dbUser.getAccount())
                            .withClaim("name", dbUser.getName())
                            .withClaim("email", dbUser.getEmail())
                            .withClaim("age", dbUser.getAge())
                            .sign(algorithm);


                    GrantResp resp = new GrantResp();
                    resp.setAccessToken(token);
                    resp.setExpire(expireDate.getTime());
                    //redis根据token找用户详情
                    String keyRedisToken = "token:" + token;
                    UserDTO userDTO = new UserDTO();
                    userDTO.setTerminal(client);
                    userDTO.setUser(dbUser);
                    userDTO.setExpireDate(expireDate.getTime());
                    userDTO.setLoginDate(now.getTime());
                    stringRedisTemplate.opsForValue().set(keyRedisToken, JSON.toJSONString(userDTO), jwtProp.getExpireHour(), TimeUnit.HOURS);

                    //redis获取在线用户的token
                    String key = "user_online:" + clientId + ":" + dbUser.getAccount();
                    stringRedisTemplate.opsForValue().set(key, token, jwtProp.getExpireHour(), TimeUnit.HOURS);


                    return ResultVO.ok(resp);

                } else {
                    return ResultVO.fail(ResultCodeEnum.ACCOUNT_PASS_FAILED);
                }
            } else {
                return ResultVO.fail(ResultCodeEnum.ACCOUNT_NOT_EXIST);
            }

        }


    }


    @PostMapping("/logout")
    @ApiOperation("退出系统")
    public ResultVO execLogoutSys(@RequestHeader String accessToken) {
        String tokenRedisKey = "token:" + accessToken;
        if (stringRedisTemplate.hasKey(tokenRedisKey)) {
            String jsonUser = stringRedisTemplate.opsForValue().get(tokenRedisKey);
            if (StringUtils.isNotBlank(jsonUser)) {
                UserDTO userDTO = JSON.parseObject(jsonUser, UserDTO.class);
                String onlineUserKey = "user_online:" + userDTO.getTerminal().getName() + ":" + userDTO.getUser().getAccount();
                if (StringUtils.isNotBlank(onlineUserKey) && stringRedisTemplate.hasKey(onlineUserKey)) {
                    stringRedisTemplate.delete(onlineUserKey);
                }
            }
            stringRedisTemplate.delete(tokenRedisKey);
        }
        return ResultVO.ok();
    }


}
